<?php

/*
 * core/service.php
 *
 * Service class handle common methods to handle service calls
 *
 */

/*
 * class ArgumentsType
 * Define a method's arguments types
 */
class ArgumentsType
{
	private static $MIN_MANDATORY = 0;
	private static $MAX_MANDATORY = 9;
	private static $MIN_OPTIONAL = 10;
	private static $MAX_OPTIONAL = 19;

	// mandatory argument types
	public static $T_INT = 0;
	public static $T_FLOAT = 1;
	public static $T_STR = 2;
	public static $T_ARR_INT = 3;
	public static $T_ARR_FLOAT = 4;
	public static $T_ARR_STR = 5;
	// optional argument types
	public static $TO_INT = 10;
	public static $TO_FLOAT = 11;
	public static $TO_STR = 12;
	public static $TO_ARR_INT = 13;
	public static $TO_ARR_FLOAT = 14;
	public static $TO_ARR_STR = 15;

	/*
	 * typeToString()
	 * return type as string
	 */
	public static function typeToString( $type )
	{
		switch( $type )
		{
			case ArgumentsType::$T_INT: return 'T_INT'; break;
			case ArgumentsType::$T_FLOAT: return 'T_FLOAT'; break;
			case ArgumentsType::$T_STR: return 'T_STR'; break;
			case ArgumentsType::$T_ARR_INT: return 'T_ARRAY_INT'; break;
			case ArgumentsType::$T_ARR_FLOAT: return 'T_ARRAY_FLOAT'; break;
			case ArgumentsType::$T_ARR_STR: return 'T_ARRAY_STR'; break;
			case ArgumentsType::$TO_INT: return 'T_OPTIONAL_INT'; break;
			case ArgumentsType::$TO_FLOAT: return 'T_OPTIONAL_FLOAT'; break;
			case ArgumentsType::$TO_STR: return 'T_OPTIONAL_STR'; break;
			case ArgumentsType::$TO_ARR_INT: return 'T_OPTIONAL_ARRAY_INT'; break;
			case ArgumentsType::$TO_ARR_FLOAT: return 'T_OPTIONAL_ARRAY_FLOAT'; break;
			case ArgumentsType::$TO_ARR_STR: return 'T_OPTIONAL_ARRAY_STR'; break;
			default:
		}
		return 'Unknown';
	}

	/*
	 * isMandatory()
	 * return true if given type is mandatory
	 */
	public static function isMandatory( $type )
	{
		return ( ($type >= ArgumentsType::$MIN_MANDATORY) && ($type <= ArgumentsType::$MAX_MANDATORY) );
	}

	/*
	 * isOptional()
	 * return true if given type is optional
	 */
	public static function isOptional( $type )
	{
		return ( ($type >= ArgumentsType::$MIN_OPTIONAL) && ($type <= ArgumentsType::$MAX_OPTIONAL) );
	}
};

// list of method's arguments type
define( 'T_INT', 					ArgumentsType::$T_INT );					// integer
define( 'T_FLOAT', 				ArgumentsType::$T_FLOAT );				// float
define( 'T_STR', 					ArgumentsType::$T_STR );					// string
define( 'T_ARR_INT',			ArgumentsType::$T_ARR_INT );			// array of integer
define( 'T_ARR_FLOAT',		ArgumentsType::$T_ARR_FLOAT );		// array of float
define( 'T_ARR_STR', 			ArgumentsType::$T_ARR_STR );			// array of string
define( 'TO_INT', 				ArgumentsType::$TO_INT );					// optional integer
define( 'TO_FLOAT', 			ArgumentsType::$TO_FLOAT );				// optional float
define( 'TO_STR', 				ArgumentsType::$TO_STR );					// optional string
define( 'TO_ARR_INT',			ArgumentsType::$TO_ARR_INT );			// optional array of integer
define( 'TO_ARR_FLOAT',		ArgumentsType::$TO_ARR_FLOAT );		// optional array of float
define( 'TO_ARR_STR', 		ArgumentsType::$TO_ARR_STR );			// optional array of string

/*
 * class Service
 */
class Service 
{
	// Service description dict[ name ] = array( 'username'=>, 'help'=>, 'public'=> );
	// Allow service manipulation without having the right upper/lower case on service name (e.g. file parsing)
	private static $registeredServices = Array();

	// Methods definition: array( array(name=>, args=>, userName=>, public=>), ... )
	private $methods = array();

	/*
	 * register()
	 * register a new service
	 */
	public static function register( $name, $userName, $help )
	{
		Service::$registeredServices[ $name ] = array( 'userName'=>$userName, 'help'=>$help );
	}

	/*
	 * getRegisteredServices()
	 * return reference to registered services
	 */
	public static function &getRegisteredServices()
	{
		return Service::$registeredServices;
	}

	/*
	 * constructor()
	 */
	public function __construct()
	{
	}

	/*
	 * registerMethod()
	 * register a service method
	 * param: name : methodName
	 * param: args : array of arguments description (ex: array(T_INT, T_STR, TO_ARR_INT))
	 * param: userName : method user name
	 * param: help : method help
	 * param: public : true if public method (no right checks)
	 */
	public function registerMethod( $name, $args, $userName, $help, $public=false )
	{
		$this->methods[ $name ] = array( 'args'=>$args, 'userName'=>$userName, 'help'=>$help, 'public'=>$public );
	}

	/*
	 * hasMethod()
	 * return true if given methodName is defined
	 */
	public function hasMethod( $methodName )
	{
		if( !array_key_exists($methodName, $this->methods) )
			return false;
		return true;
	}

	/*
	 * isPublicMethod()
	 * return true if given methodName exists and is public
	 */
	public function isPublicMethod( $methodName )
	{
		if( !array_key_exists($methodName, $this->methods) )
			return false;
		if( $this->methods[$methodName]['public'] )
			return true;
		return false;
	}

	/*
	 * checkArguments()
	 * check method arguments with real arguments
	 */
	public function checkArguments( $methodName, $args )
	{
		function checkType( $type, $value )
		{
			switch( $type )
			{
				case T_INT: return is_numeric($value); break;
				case T_FLOAT: return is_numeric($value); break;
				case T_STR: return is_string($value); break;
				case T_ARR_INT: foreach( $value as $v ) { if( !is_numeric($v) ) return false; } return true; break;
				case T_ARR_FLOAT: foreach( $value as $v ) { if( !is_numeric($v) ) return false; } return true; break;
				case T_ARR_STR: foreach( $value as $v ) { if( !is_string($v) ) return false; } return true; break;
				case TO_INT: return is_numeric($value); break;
				case TO_FLOAT: return is_numeric($value); break;
				case TO_STR: return is_string($value); break;
				case TO_ARR_INT: foreach( $value as $v ) { if( !is_numeric($v) ) return false; } return true; break;
				case TO_ARR_FLOAT: foreach( $value as $v ) { if( !is_numeric($v) ) return false; } return true; break;
				case TO_ARR_STR: foreach( $value as $v ) { if( !is_string($v) ) return false; } return true; break;
				default:
			}
			return false;
		}

		$expected =& $this->methods[$methodName]['args'];
		$nbExpected = sizeof( $expected );
		$nbArgs = sizeof( $args );
		$nbOptional = 0;
		foreach( $expected as $type )
		{
			$nbOptional += (ArgumentsType::isOptional($type)) ? 1 : 0;
		}

		// nb of arguments
		if( ($nbArgs < ($nbExpected-$nbOptional)) || ($nbArgs > $nbExpected) )
		{
			if( Conf::$c['debug']['active'] )
			{
				Log::error( 'wrong number of arguments' );
			}
			return false;
		}

		$error = false;
		$i = 0;
		$n = $nbArgs;
		while( $i < $n )
		{
			if( !checkType($expected[$i], $args[$i]) )
			{
				if( Conf::$c['debug']['active'] )
				{
					$typeStr = ArgumentsType::typeToString( $expected[$i] );
					Log::error( 'wrong type for argument '.$i.' ('.$args[$i].') exptected '.$typeStr.'.' );
				}
				$error = true;
				break;
			}
			$i++;
		}

		return !$error; 
	}

	// return: array( serviceID, array(non_public_methods_IDs) )
	public function updateDatabase()
	{
		require_once( 'database_fct.php' );

		$serviceId = -1;
		$serviceName = get_class( $this );
		$serviceDesc = Service::$registeredServices[ $serviceName ];
		$methodId = -1;
		$methodIds = array();

		// registering service itself
		if( ($serviceId = CoreDBFct::isServiceExist($serviceName)) >= 0 )
		{
			CoreDBFct::updateService( $serviceName, $serviceDesc['userName'], $serviceDesc['help'] );
			print( 'Service.updateDatabase(): updating service: '.$serviceName.' id: '.$serviceId.' <br>'."\n" );
		}
		else
		{
			$serviceId = CoreDBFct::insertService( $serviceName, $serviceDesc['userName'], $serviceDesc['help'] );
			print( 'Service.updateDatabase(): inserting service: '.$serviceName."<br>\n" );
		}

		// registering methods
		foreach( $this->methods as $methodName => $methodDesc )
		{
			if( ($methodId = CoreDBFct::isServiceMethodExist($serviceId, $methodName)) >= 0 )
			{
				CoreDBFct::updateServiceMethod( $serviceId, $methodName, $methodDesc['userName'], $methodDesc['help'], $methodDesc['public'] );
				print( 'Service.updateDatabase(): updating method: '.$serviceName.'.'.$methodName.'() id: '.$methodId.' public: '.(($methodDesc['public'])?'TRUE':'FALSE').'<br>'."\n" );
			}
			else
			{
				$methodId = CoreDBFct::insertServiceMethod( $serviceId, $methodName, $methodDesc['userName'], $methodDesc['help'], $methodDesc['public'] );
				print( 'Service.updateDatabase(): inserting method: '.$serviceName.'.'.$methodName.'() id: '.$methodId.' public: '.(($methodDesc['public'])?'TRUE':'FALSE').'<br>'."\n" );
			}

			if( !$methodDesc['public'] )
				array_push( $methodIds, $methodId );
		}

		return array( $serviceId, $methodIds );
	}

	/*
	 * findServiceFiles()
	 * Find recursively service filenames defined in given rootDirectory
	 */
	public static function findServiceFiles( $rootDirectory )
	{
		$fileList = array();

		// TODO: check that rootDirectory isn't precede with '/' or '../'
		if( !file_exists($rootDirectory) )
		{
			//Log::fatal( 'directory ('.$rootDirectory.') does not exist. <br>' );
			return $fileList;
		}

		$files = scandir( $rootDirectory );

		foreach( $files as $file )
		{
			// reject ., .., and non-php-service file
			if( ($file == '.') || ($file == '..') ) continue;

			if( is_dir($rootDirectory.'/'.$file) )
			{
				$dirFiles = Service::findServiceFiles( $rootDirectory.'/'.$file );
				foreach( $dirFiles as $f )
				{
					array_push( $fileList, $f );
				}
			}
			else
			{
				if( !ereg( '_service.php$', $file ) ) continue;
				array_push( $fileList, $rootDirectory.'/'.$file );
			}
		}

		return $fileList;
	}

}




?>
